--[[--------------------------------------------------
SciTE Smart braces
Version: 1.3.1
Authors: Dmitry Maslov, Julgo, TymurGubayev
-------------------------------------------------
Works if:

 Connected to Startup
 In the settings found braces.autoclose = 1 
 In the settings found braces.open = The opening brace
 In the settings found braces.close = The closing bracket
 Used only in the Russian assembly because advanced function OnKey

 parameter braces.multiline determine the list of names lexers (separated by commas) for which the brace is inserted into three lines with the cursor in the middle. by default braces.multiline=cpp

-------------------------------------------------
functional:

 Auto-brackets
 Auto-selected text in brackets
 Special treatment of {and} in cpp: automatic indents

-------------------------------------------------
The logic of the:

 The script works only if braces.autoclose = 1

 If we introduce the character of braces.open, it is automatically inserted into a pair of braces.close, so the cursor is between the brackets

 If we enter the closing bracket of braces.close and the next character, the same closing bracket, the input is swallowed and extra closing, the same closing bracket, the input is swallowed and extra closing

 If we select the text and we enter a character from braces.open,
 the text is framed by quotes braces.open - braces.close
 If he was framed by quotation marks, they are removed,
 consideration being given to a line break, ie if the selected
 text ends with a newline, the brackets are inserted before the transfer line
 
 If we introduce the {character when editing cpp, it automatically inserts a line break twice, and after the} - the cursor, it turns
in the middle, that is, after the first line break, all margins remain

 If we insert a symbol} when editing cpp, the indent
automatically reduced by one

 If we just put in the bracket automatically, as soon as we press BACK_SPACE removed inserted bracket, ie works as DEL, and not as BACK_SPACE

 If you insert a bracket whose braces.open == braces.close,
 couple is inserted only if there is even a line of brackets

 WARNING: The script uses the F-tion string.pattern of COMMON.lua
--]]--------------------------------------------------

-- Returns the current line character
local function GetEOL()
	local eol = "\r\n"
	if editor.EOLMode == SC_EOL_CR then
		eol = "\r"
	elseif editor.EOLMode == SC_EOL_LF then
		eol = "\n"
	end
	return eol
end

local function FindCount( text, textToFind )
	local count = 0;
	for _ in string.gmatch( text, textToFind:pattern() ) do
		count = count + 1
	end
	return count
end

-- position is the beginning of the line (including padding)
local function IsLineStartPos( pos )
	return ( editor.LineIndentPosition[editor:LineFromPosition(pos)] == pos )
end

-- Get the current line number
local function GetCurrLineNumber()
	return editor:LineFromPosition( editor.CurrentPos )
end

-- Get indented line
local function GetLineIndentation( num_line )
	if ( num_line < 0 ) then num_line = 0 end
	if ( num_line >= editor.LineCount ) then num_line = editor.LineCount - 1 end
	return ( editor.LineIndentation[num_line] / editor.Indent )
end

-- the last in line?
local function IsInLineEnd( num_line, text )
	local endpos = editor.LineEndPosition[num_line]
	if	( endpos >= string.len( text ) )
		and
		string.find( editor:textrange( editor:PositionBefore( endpos - string.len( text ) + 1 ), endpos ), text:pattern() )
	then
		return true
	end
	return false
end

-- last character in the line - the line end?
local function IsEOLlast( text )
	-- at the end of the Valois line is always one character
--[[	if string.find( text, GetEOL(), string.len( text ) - 1 ) then
		return true
	end
	return false]]
	return (text:sub(-1) == GetEOL())
end

-- following the position of the text
local function nextIs(pos, text)
	if ( string.find( editor:textrange( pos, editor:PositionAfter( pos + string.len( text ) - 1 ) ), text:pattern() ) ) then
		return true
	end
	return false
end

-- the next character position end of the line?
local function nextIsEOL(pos)
	if	( pos == editor.Length )
		or
		( nextIs( pos, GetEOL() ) )
	then
		return true
	end
	return false
end

-----------------------------------------------------------------
-- checks brackets and set bracebegin braceend in s for
-- balance: "(x) y (z)" -> true, "x) y (z" -> false
local function BracesBalanced (s, bracebegin, braceend)
	if (#bracebegin + #braceend) > 2 then
		--@warn: This function will not work with "brackets" more than one character.
		--@todo: for "long" brackets to rewrite this function to lpeg. But to whom it should be? ..
		return true
	end
	local b,e    = s:find("%b"..bracebegin..braceend)
	local b2 = s:find(bracebegin, 1, true)
	local e2 = s:find(braceend, 1, true)
	return (b == b2) and (e == e2)
end -- BracesBalanced

local function BlockBraces( bracebegin, braceend )
	local text = editor:GetSelText()
	local selbegin = editor.SelectionStart
	local selend = editor.SelectionEnd
	local b, e   = string.find( text, "^%s*"..bracebegin:pattern() )
	local b2, e2 = string.find( text, braceend:pattern().."%s*$" )
	local add = ( IsEOLlast( text ) and GetEOL() ) or ""
	
	editor:BeginUndoAction()
	if (b and b2) and BracesBalanced( text:sub( e+1, b2-1 ) , bracebegin, braceend ) then
		text = string.sub( text, e+1, b2-1 )
		editor:ReplaceSel( text..add )
		editor:SetSel( selbegin, selbegin + #( text..add ) )
	else
		editor:insert( selend - #add, braceend )
		editor:insert( selbegin, bracebegin )
		editor:SetSel( selbegin, selend + #( bracebegin..braceend ) )
	end
	editor:EndUndoAction()
	
	return true
end

local function GetIndexFindCharInProps( value, findchar )
	if findchar then
		local resIndex = string.find( props[value], findchar:pattern() , 1 )
		if	( resIndex ~= nil )
			and
			( string.sub( props[value], resIndex,resIndex ) == findchar )
		then
			return resIndex
		end
	end
	return nil
end

local function GetCharInProps( value, index )
	return string.sub( props[value], index, index )
end

-- returns the open parenthesis and closing parenthesis
-- on incoming character, ie for example,
-- if the input ')' at the output '(' ')'
-- if the input '(' then the output '(' ')'
local function GetBraces( char )
	local braceOpen = ''
	local braceClose = ''
	local symE = ''
	local brIdx = GetIndexFindCharInProps( 'braces.open', char )
	if ( brIdx ~= nil ) then
		symE = GetCharInProps( 'braces.close', brIdx )
		if ( symE ~= nil ) then 
			braceOpen = char
			braceClose = symE
		end
	else
		brIdx = GetIndexFindCharInProps( 'braces.close', char )
		if ( brIdx ~= nil ) then
			symE = GetCharInProps( 'braces.open', brIdx )
			if ( symE ~= nil ) then 
				braceOpen = symE
				braceClose = char
			end
		end
	end
	return braceOpen, braceClose
end

local g_isPastedBraceClose = false

-- "smart brackets / quotes"
-- returns true if the character is not processed further to
local function SmartBraces( char )
	local multiline = props['braces.multiline']
	if multiline == '' then multiline = 'cpp' end
	local use_multiline = string.find(','..multiline..',', ','..props['Language']..',')

	if ( props['braces.autoclose'] == '1' ) then
		local isSelection = editor.SelectionStart ~= editor.SelectionEnd
		-- find delimiters
		local braceOpen, braceClose = GetBraces(char)
		if ( braceOpen ~= '' and braceClose ~= '' ) then
			-- check whether we have selected any text
			if ( isSelection == true ) then
				-- making process on Auto-Text brackets
				return BlockBraces( braceOpen, braceClose )
			else
				-- If the next character closing bracket
				-- and we will introduce its input swallow
				local nextsymbol = string.format( "%c", editor.CharAt[editor.CurrentPos] )
				if	( GetIndexFindCharInProps( 'braces.close', nextsymbol ) ~= nil )
					and
					( nextsymbol == char )
				then 
					editor:CharRight()
					return true
				end
				-- If we set and the open parenthesis
				-- next line breaks or is paired closing bracket
				-- then immediately insert a closing parenthesis
				if	( char == braceOpen )
					and
					( nextIsEOL( editor.CurrentPos ) or nextIs( editor.CurrentPos, braceClose ) )
				then
					-- the magical process the bracket {in cpp
					if	( char == '{' ) and
						( use_multiline )
					then
						editor:BeginUndoAction()
						local ln = GetCurrLineNumber()
						if	( ln > 0 and GetLineIndentation( ln ) > GetLineIndentation( ln - 1 ) )
							and
							( IsLineStartPos( editor.CurrentPos ) )
							and 
							( not IsInLineEnd( ln-1, '{' ) )
						then
							editor:BackTab()
						end
						editor:AddText( '{' )
						editor:NewLine()
						if ( GetLineIndentation( ln ) == GetLineIndentation( ln + 1 ) ) then
							editor:Tab()
						end
						local pos = editor.CurrentPos
						editor:NewLine()
						if ( GetLineIndentation( ln + 2 ) == GetLineIndentation( ln + 1 ) ) then
							editor:BackTab()
						end
						editor:AddText( '}' )
						editor:GotoPos( pos )
						editor:EndUndoAction()
						return true
					end
					-- If you insert the bracket with the same right and left, then look whether there is already open in the string
					if	( braceOpen == braceClose )
						and
						( math.fmod( FindCount( editor:GetCurLine(), braceOpen ), 2 ) == 1 )
					then
						return false
					end
					-- Insert Closing Brackets
					editor:BeginUndoAction()
					editor:InsertText( editor.CurrentPos, braceClose )
					editor:EndUndoAction()
					g_isPastedBraceClose = true
				end
				-- if we put a closing bracket
				if ( char == braceClose ) then
					-- "the magical" handle bracket} in cpp
					if ( char == '}' ) and
						( use_multiline )
					then
						editor:BeginUndoAction()
						if (IsLineStartPos( editor.CurrentPos ) )
						then
							editor:BackTab()
						end
						editor:AddText( '}' )
						editor:EndUndoAction()
						return true
					end
				end
			end
		end
	end
	return false
end

-- Intercept function editor OnKey
AddEventHandler("OnKey", function(key, shift, ctrl, alt, char)
	if ( editor.Focus ) then
		if ( key == 8 and g_isPastedBraceClose == true ) then -- VK_BACK (08)
			g_isPastedBraceClose = false
			editor:BeginUndoAction()
			editor:CharRight()
			editor:DeleteBack()
			editor:EndUndoAction()
			return true
		end

		g_isPastedBraceClose = false

		if ( char ~= '' ) then
			return SmartBraces( char )
		end
	end
end)
